《windows PE》PE文件头

第一部分 预备知识

四类地址

  • 虚拟地址(VA)
  • 相对虚拟内存地址(RVA)
  • 文件偏移地址(FOA)
  • 特殊地址          虚拟地址:PE文件被加载到内存,PE对应的进程拥有了4GB的空间,这个空间存在的地址就是虚拟地址。
             相对虚拟地址:相对与基地址的偏移量,RVA的存在是由于dll(模块)的基地址的不同而产生的。
             文件偏移地址:文件中某个位置距离文件头的偏移量。
             特殊位置,不细考究。

数据目录

         数据目录记录的是整个PE结构中存在的数据类型。

         节就是存放不同的类型的数据,不同的节区有不同的访问权限。

对齐

  • 内存对齐
  • 文件对齐
  • 资源数据对齐
             内存对齐:windows中,内存属性的基本单位是页,在32位系统是4KB(1000h),在64位系统中是8kb。
             文件对齐:为了提高磁盘利用率,把一个物理扇区作为一个对齐粒度的大小,也就是12字节(200H),这是每个数据段都是200H的整数倍的原因。

第二部分 32位windows系统的PE结构

定位标准的PE头

         由于DOS stub是一个不确定的长度,所以导致DOS头也是一个不确定的长度,这时候,我们采用e_lfanew字段来定位后续的PE结构位置。该字段是一个偏移量。PE头的定位遵循一下公式:PE_Start=DOS_MZ+IMAGE_HANDER.e_lfanew.
         PE文件结构:

第三部分 PE文件头部解析

DOS MZ头

         MZ头下有两个需要知道的成员。一个是e_magic ,一个是e_lfanew

PE头标志 Signature

         Signature标志位于DOS_STUB之后,该标志位于e_lfanew所指向的位置。内容固定,对应的ASCII是”PE\0\0”。

标准PE头 IMAGE_FILE_HEADER

         位置:在PE文件头标志的后面,位于e_lfanew+4的位置。
         大小:占据了20个字节。
         作用:他记录了PE文件的全局属性(运行的平台,PE文件的类型,文件中存在的节区总数)
         以下是FILE_HEADER的成员信息:

1
2
3
4
5
6
7
8
9
10
typedef struct _IMAGE_FILE_HEADER
{
WORD Machine; //运行平台
WORD NumberOfSections; //节区数量
DWORD TimeDateStamp; //创建时间
DWORD PointerToSymbolTable; //指向符号表
DWORD NumberOfSymbols; //符号表符号数
WORD SizeOfOptionalHeader; //拓展头长度
WORD Characteristics; //文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

  • Machime:该文件的运行平台,是x86、x64还是I64等等,可以是下面值里的某一个。
  • TimeDateStamp:PE文件的创建时间,一般有连接器填写
  • NumberOfSections:该PE文件中有多少个节,也就是节表中的项数
  • SizeOfOptionalHeader:紧随其后的可选头的大小。

拓展PE头IMAGE_OPTIONAL_HEADER32

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
typedef struct _IMAGE_OPTIONAL_HEADER
{
// Standard fields.
WORD Magic; // 标志字, ROM 映像(0107h),普通可执行文件(010Bh)
BYTE MajorLinkerVersion; // 链接程序的主版本号
BYTE MinorLinkerVersion; // 链接程序的次版本号
DWORD SizeOfCode; // 所有含代码的节的总大小
DWORD SizeOfInitializedData; // 所有含已初始化数据的节的总大小
DWORD SizeOfUninitializedData; // 所有含未初始化数据的节的大小
DWORD AddressOfEntryPoint; // 程序执行入口RVA
DWORD BaseOfCode; // 代码的区块的起始RVA
DWORD BaseOfData; // 数据的区块的起始RVA
// NT additional fields. 以下是属于NT结构增加的领域。
DWORD ImageBase; // 程序的首选装载地址
DWORD SectionAlignment; // 内存中的区块的对齐大小
DWORD FileAlignment; // 文件中的区块的对齐大小
WORD MajorOperatingSystemVersion; // 要求操作系统最低版本号的主版本号
WORD MinorOperatingSystemVersion; // 要求操作系统最低版本号的副版本号
WORD MajorImageVersion; // 可运行于操作系统的主版本号
WORD MinorImageVersion; // 可运行于操作系统的次版本号
WORD MajorSubsystemVersion; // 要求最低子系统版本的主版本号
WORD MinorSubsystemVersion; // 要求最低子系统版本的次版本号
DWORD Win32VersionValue; // 莫须有字段,不被病毒利用的话一般为0
DWORD SizeOfImage; // 映像装入内存后的总尺寸
DWORD SizeOfHeaders; // 所有头+ 区块表的尺寸大小
DWORD CheckSum; // 映像的校检和
WORD Subsystem; // 可执行文件期望的子系统
WORD DllCharacteristics; // DllMain()函数何时被调用,默认为0
DWORD SizeOfStackReserve; // 初始化时的栈大小
DWORD SizeOfStackCommit; // 初始化时实际提交的栈大小
DWORD SizeOfHeapReserve; // 初始化时保留的堆大小
DWORD SizeOfHeapCommit; // 初始化时实际提交的堆大小
DWORD LoaderFlags; // 与调试有关,默认为0
DWORD NumberOfRvaAndSizes; // 下边数据目录的项数,这个字段自Windows NT 发布以来 一直是16
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; // 数据目录表
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

         需要我们熟悉的是以下几个成员。

  • AddressOfEntryPoint; // 程序执行入口RVA
  • BaseOfCode; // 代码的区块的起始RVA
  • DWORD ImageBase; // 程序的首选装载地址
  • SectionAlignment; // 内存中的区块的对齐大小
  • FileAlignment; // 文件中的区块的对齐大小
  • SizeOfImage; // 映像装入内存后的总尺寸
  • DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; // 数据目录表

IMAGE_NT_HEADERS

         包括三个部分组成:PE头标志,IMAGE_FILE_HEADER,IMAGE_OPTIOANAL_HEADER.

数据目录项 IMAGE_DATA_DIRECTORY

         NT3.1开始,数据目录一共有16种,使用IMAGE_DATA_DIRECTORY来定义每种数据。

1
2
3
4
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //相对虚拟地址
DWORD Size;      //大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

##节表项IMAGE_SECTION_HEADER

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct _IMAGE_SECTION_HEADER
{
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];//节区名称
union {
DWORD PhysicalAddress;
DWORD VirtualSize; //节区大小
} Misc;
DWORD VirtualAddress; //节区的RVA
DWORD SizeOfRawData; //在文件中对齐的尺寸
DWORD PointerToRawData; //在文件中的偏移
DWORD PointerToRelocations; //指向重定位表的指针
DWORD PointerToLinenumbers;
WORD NumberOfRelocations; //重定位表的数目
WORD NumberOfLinenumbers;
DWORD Characteristics; //节区表的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

第三部分 详细解析PE文件头字段

IMAGE_FILE_HEADER字段

  • Machine:
    • 位置:+0004H,
    • 大小:单字
    • 作用:查看文件可以在那种机器上运行。
  • NumberOfSection:
    • 位置:+0006h
    • 大小:单字
    • 作用:文件中存在的节区的数目,数值不能小于1,但是节区数可以为0.
  • TimeDateStamp
    • 位置:+0008h
    • 大小:双字
    • 作用:编译时间,压缩时被修改。
  • SizeofOptionalHeader
    • 位置:+0014h
    • 大小:单字
    • 作用:指向的是OptionalHeader的大小。默认是00e0H,在64位是00f0H
  • Character
    • 位置:+0016h
    • 大小:单字
    • 作用:标志这PE文件的类型,如果这个值是010fh的话,表示这是一个EXE文件,如果值是210eh,表示这个值是DLL文件。

IMAGE_OPTIONAL_HEADER字段

  • Magic:表示该文件的类型:如果是010Bh,表示文件是32位,如果是0107H,表示文件是ROM,如果是020BH,表示文件是64位PE
  • AddressOfEntryPoint :表示启动代码距离PE加载后的初始位置的偏移量,如果病毒需要在程序中启动恶意代码,则需要修改这一成员的数值。但是对于DLL文件,这一成员你的数值为0,因为不存在入口点。
  • BaseOfCode:代码段的起始RVA,但是不一定是程序的入口点。
  • ImageBase:PE文件的加载基地址。exe文件通常是00400000.
  • SizeOfImade:表示内存中整个PE文件的映射尺寸。
  • DllCharacteristics:DLL文件属性,但是不单纯是针对DLL文件,对于所有PE文件同样有效。
  • DataDirectory:目录数据,表示不同的节区的基本属性(RVA和size)。

IMAGE_SECTION_HEADER

  • Name[IMAGE_SIZEOF_SHORT_NAME]: 八个字节的节去名称。
  • VirtualAddress:节区的RVA
  • SizeOfRAWDATA:节区对齐后的尺寸
  • PointOfRAWDATA:节区数据在文件中的偏移量。
  • PointerToRelocations:重定位表的指针
  • NumberOfRelocations:重定位表的数目